/*==============================================
 * Improved X Mesh loader for GDEV Engine
 *
 * First version by Mark Gossage
 *==============================================*/
#pragma once
#include <string>
#include "XMesh.h"
#include "Fail.h"

CXMesh::CXMesh(LPDIRECT3DDEVICE9 pDev,const char * name)
{
	mpDev=pDev;
	mpMesh=0;

	LoadMesh(name);
	TidyMesh();	///< optimisation & repairs
	ComputeCollisionInfo();	///< computes the bounding sphere
}

CXMesh::~CXMesh()
{
	//TODO(~CXMesh());
	for(unsigned i = 0; i < mMats.size(); i++)
	{
		if(mTextures[i] != NULL)
			mTextures[i]->Release();
	}
    // release the mesh
	if (mpMesh)
		mpMesh->Release();	
    // clear the vectors
	mMats.clear();
	mTextures.clear();	
}

bool CXMesh::LoadMesh(const char* name)
{
	//TODO(LoadMesh);
	// variable to hold the return code
	HRESULT hr;
	// variable to hold the loaded mesh
	//LPD3DXMESH pMeshSysMem;
	// buffer to hold the adjacency data
	LPD3DXBUFFER ppAdjacencyBuffer;
	// buffer to hold materials
	LPD3DXBUFFER pD3DXMtrlBuffer;
	DWORD pNumMaterials;
	// Load the mesh from the disk
	hr = D3DXLoadMeshFromX (name,
		D3DXMESH_SYSTEMMEM,
		mpDev,
		&ppAdjacencyBuffer,
		&pD3DXMtrlBuffer,
		NULL,
		&pNumMaterials,
		&mpMesh);
	// Check the return code to make sure the mesh was loaded successfully
	if (FAILED(hr))
		return false;

	// Get a pointer to the material buffer within the mesh
	D3DXMATERIAL* matMaterials= (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();
	mMats.resize(pNumMaterials);
	// Loop through the materials in the mesh and create a D3DMATERIAL for each one
	for(DWORD i = 0; i < pNumMaterials; i++)
	{
		//Copy the material
		mMats[i] = matMaterials[i].MatD3D;
		//Set the ambient color for the material (D3DX does not do this)
		mMats[i].Ambient = mMats[i].Diffuse;
	}

	mTextures.resize(pNumMaterials);
	for(unsigned i = 0; i < pNumMaterials; i++)
	{
		if(matMaterials[i].pTextureFilename == NULL)
		{
			mTextures[i] = NULL;		// no texture
		}
		else
		{
			// there is a texture
			std::string s = "media/";
			s += matMaterials[i].pTextureFilename;
			hr = D3DXCreateTextureFromFile(mpDev,s.c_str(),&mTextures[i]);
			if FAILED (hr)
				return false;
		}
	}
	ppAdjacencyBuffer->Release();
	pD3DXMtrlBuffer->Release();

	return true;
}

void CXMesh::TidyMesh()	///< optimisation & repairs
{
	if (mpMesh==NULL)	return;	// sanity

	// Does the mesh have a D3DFVF_NORMAL in its vertex format?
	if( !( mpMesh->GetFVF( ) & D3DFVF_NORMAL ) )
	{
		// No, so clone a new mesh and add D3DFVF_NORMAL to its vertex format:
		ID3DXMesh* theTempMesh = NULL;
		mpMesh->CloneMeshFVF( D3DXMESH_MANAGED, 
								mpMesh->GetFVF( ) | D3DFVF_NORMAL, // add normals to FVF here
								mpDev, 
								&theTempMesh );
		// Compute the normals using the mesh; this will insert normals into the mesh.
		D3DXComputeNormals( theTempMesh, NULL );
		// Dispose of old mesh
		mpMesh->Release( );
		// Reassign our pointer to the new generated mesh with normals.
		mpMesh = theTempMesh;
	}
}

void CXMesh::ComputeCollisionInfo()	///< computes the bounding sphere
{
	if (mpMesh==NULL)	return;	// sanity
	D3DXVECTOR3* pVertices=NULL;

	if (FAILED(mpMesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*)&pVertices)))
	{
		mRadius=1.0f;	// a guess?
		return;
	}

	mRadius=0;
	D3DXVECTOR3 centre;
	D3DXComputeBoundingSphere(pVertices,	// the vertices
						mpMesh->GetNumVertices(),	// number of them 
						D3DXGetFVFVertexSize(mpMesh->GetFVF()),	// the struct size
						&centre,	// centre
						&mRadius);	// radius
	mpMesh->UnlockVertexBuffer();
}

void CXMesh::Draw()
{
	//TODO(Draw());
	for(int i = 0; i < GetNumMaterial(); i++)
	{
		// set texture, set material
		mpDev->SetTexture(0,mTextures[i]);
		mpDev->SetMaterial(&mMats[i]);
		// Draw the current mesh
		mpMesh->DrawSubset(i);
	}
}

void CXMesh::Draw(const D3DXMATRIX& mat)	// renders at matrix
{
	//TODO(Draw(D3DXMATRIX& mat));
	// hint: set the matrix, then call Draw();
	mpDev->SetTransform(D3DTS_WORLD,&mat);
	Draw();
}
void CXMesh::Draw(const D3DXVECTOR3& pos,float scale)	// at some position
{
	//TODO(Draw(const D3DXVECTOR3& pos,float scale));
	// hint: create the matrix, then call Draw(D3DXMATRIX& mat);
	D3DXMATRIX matFinal, matTranslate, matScale;
	D3DXMatrixTranslation(&matTranslate, pos.x, pos.y, pos.z);
	D3DXMatrixScaling(&matScale, scale, scale, scale);
	D3DXMatrixMultiply(&matFinal, &matScale, &matTranslate);
	Draw(matFinal);
}
